/*
 * SPDX-License-Identifier: LGPL-2.1-or-later
 * Copyright Red Hat Inc. and Hibernate Authors
 */
package org.hibernate.orm.test.mapping.formula;

import java.io.Serializable;
import java.util.List;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;

import org.hibernate.annotations.Formula;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;

import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/**
 * Test that the queries generated by {@link Formula} properly ignore type names when escaping identifiers.
 * <p>
 * Created by Michael Hum on 17/07/2015.
 */
@RequiresDialect(H2Dialect.class)
public class FormulaWithColumnTypesTest extends BaseCoreFunctionalTestCase {

	@Override
	protected void configure(Configuration configuration) {
		configuration.setProperty( Environment.DIALECT, ExtendedDialect.class );
	}

	@Test
	@JiraKey(value = "HHH-9951")
	public void testFormulaAnnotationWithTypeNames() {

		inTransaction( session -> {
			session.getSessionFactory().getQueryEngine().getInterpretationCache().close();

			final DisplayItem displayItem20 = new DisplayItem();
			displayItem20.setDisplayCode( "20" );

			final DisplayItem displayItem03 = new DisplayItem();
			displayItem03.setDisplayCode( "03" );

			final DisplayItem displayItem100 = new DisplayItem();
			displayItem100.setDisplayCode( "100" );

			session.persist( displayItem20 );
			session.persist( displayItem03 );
			session.persist( displayItem100 );
		} );

		// 1. Default sorting by display code natural ordering (resulting in 3-100-20).
		inTransaction( session -> {
			session.getSessionFactory().getQueryEngine().getInterpretationCache().close();

			final CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
			final CriteriaQuery<DisplayItem> criteria = criteriaBuilder.createQuery( DisplayItem.class );
			final Root<DisplayItem> root = criteria.from( DisplayItem.class );
			criteria.orderBy( criteriaBuilder.asc( root.get( "displayCode" ) ) );

			final List<DisplayItem> displayItems = session.createQuery( criteria ).getResultList();

//					List displayItems = session.createCriteria( DisplayItem.class )
//							.addOrder( Order.asc( "displayCode" ) )
//							.list();

			assertNotNull( displayItems );
			assertEquals( 3, displayItems.size() );
			assertEquals(
					"03",
					displayItems.get( 0 ).getDisplayCode()
			);
			assertEquals(
					"100",
					displayItems.get( 1 ).getDisplayCode()
			);
			assertEquals(
					"20",
					displayItems.get( 2 ).getDisplayCode()
			);
		} );

		// 2. Sorting by the casted type (resulting in 3-20-100).
		inTransaction( session -> {
			session.getSessionFactory().getQueryEngine().getInterpretationCache().close();

			final CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
			final CriteriaQuery<DisplayItem> criteria = criteriaBuilder.createQuery( DisplayItem.class );
			final Root<DisplayItem> root = criteria.from( DisplayItem.class );
			criteria.orderBy( criteriaBuilder.asc( root.get( "displayCodeAsInteger" ) ) );

			final List<DisplayItem> displayItemsSortedByInteger = session.createQuery( criteria ).getResultList();

//					List displayItemsSortedByInteger = session.createCriteria( DisplayItem.class )
//							.addOrder( Order.asc( "displayCodeAsInteger" ) )
//							.list();

			assertNotNull( displayItemsSortedByInteger );
			assertEquals( 3, displayItemsSortedByInteger.size() );
			assertEquals(
					"03",
					displayItemsSortedByInteger.get( 0 ).getDisplayCode()
			);
			assertEquals(
					"20",
					displayItemsSortedByInteger.get( 1 ).getDisplayCode()
			);
			assertEquals(
					"100",
					displayItemsSortedByInteger.get( 2 ).getDisplayCode()
			);
		} );
	}

	@Override
	public Class<?>[] getAnnotatedClasses() {
		return new Class<?>[] { DisplayItem.class };
	}

	/**
	 * Test entity for formulas.
	 * <p>
	 * INTEGER is registered as a keyword for testing lower-case sensitivity.
	 * FLOAT is registered as a valid column type with oracle dialects.
	 * <p>
	 * Created by Michael Hum on 17/07/2015.
	 */
	@Entity(name = "DisplayItem")
	public static class DisplayItem implements Serializable {

		private int id;

		private String displayCode;

		private Integer displayCodeAsInteger;

		private Integer displayCodeAsFloat;

		@Id
		@GeneratedValue
		public int getId() {
			return id;
		}

		public void setId(int id) {
			this.id = id;
		}

		@Column(name = "DISPLAY_CODE")
		public String getDisplayCode() {
			return this.displayCode;
		}

		public void setDisplayCode(final String displayCode) {
			this.displayCode = displayCode;
		}

		@Formula("CAST(DISPLAY_CODE AS FLOAT)")
		public Integer getDisplayCodeAsFloat() {
			return displayCodeAsFloat;
		}

		public void setDisplayCodeAsFloat(final Integer displayCodeAsFloat) {
			this.displayCodeAsFloat = displayCodeAsFloat;
		}

		@Formula("CAST(DISPLAY_CODE AS INTEGER)")
		public Integer getDisplayCodeAsInteger() {
			return displayCodeAsInteger;
		}

		public void setDisplayCodeAsInteger(final Integer displayCodeAsInteger) {
			this.displayCodeAsInteger = displayCodeAsInteger;
		}
	}

	/**
	 * Dialect for test case where we register a keyword and see if it gets escaped or not.
	 * <p>
	 * Created by Mike on 18/07/2015.
	 */
	public static class ExtendedDialect extends H2Dialect {

		public ExtendedDialect() {
			super();
		}

		public ExtendedDialect(DatabaseVersion version) {
			super( version );
		}

		public ExtendedDialect(DialectResolutionInfo info) {
			super( info );
		}

		@Override
		protected void registerDefaultKeywords() {
			super.registerDefaultKeywords();
			registerKeyword( "FLOAT" );
			registerKeyword( "INTEGER" );
		}
	}
}
